﻿using BlazorAdmin.Generator.Models;
using BlazorAdmin.Generator.Outputs;
using BootstrapBlazor.Components;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace BlazorAdmin.Generator.Generators
{
    public class PetaPocoRazorStyleCodes: Generator
    {
        private string RootPath = AppDomain.CurrentDomain.BaseDirectory.Replace("\\", "/");

        public override async Task<string> RunAsync(GenerateSetting setting, Func<string, Color?, Task> console)
        {
            CreateCodeFolders(setting);

            await console($"Create Code Folder {setting.TimePath} Success ", Color.Success);

            var tables = new List<string>();
            if (!string.IsNullOrWhiteSpace(setting.GenTableNames))
                tables.AddRange(setting.GenTableNames.Split(",", StringSplitOptions.RemoveEmptyEntries));

            InitSysParams(setting);

            foreach (var tableName in tables)
            {
                await console($"--------->> Table [{setting.TableName}] Generated Start <<---------", Color.Warning);

                setting.TableName = tableName;

                (string msg, List<Column> columns) = GetTableColumns(setting);

                if (!string.IsNullOrEmpty(msg))
                {
                    await console($"--------->> Table [{setting.TableName}] Generated Error，Can Not Read Table Column Info <<---------\n{msg}", Color.Danger);
                    return msg;
                }
                else
                {
                    await console($"Table [{setting.TableName}] Column Info Loaded", Color.Success);
                }

                ParamDic[EnumParam.TableName.ToString()] = setting.TableName;

                // 表名含有下划线转成Pascal大小写，属性也转换成小写，默认删除表名前缀 t_
                setting.ParscalTableName = ParamDic[EnumParam.ParscalTableName.ToString()] = ToPascalName(setting.TableName);

                //过滤主键ID、属性为空的
                columns = columns.Where(t => t.IsKey == false && !string.IsNullOrEmpty(t.PropertyCode)).ToList();

                GenerateEntity(setting, columns);

                await console($"Table [{setting.TableName}] Entity Files Created", Color.Success);

                //columns = columns.Where(t => t.InputType != EnumInputType.UnKnow).ToList();

                GenerateRazorComponentEdit(setting, columns);

                await console($"Table [{setting.TableName}] Razor Component Edit Files Created", Color.Success);

                GenerateRazorComponentDetail(setting, columns);

                await console($"Table [{setting.TableName}] Razor Component Detail Files Created", Color.Success);

                GenerateRazorModel(setting, columns);

                await console($"Table [{setting.TableName}] Razor Model Files Created", Color.Success);

                GenerateRazorComponentSearch(setting, columns);

                await console($"Table [{setting.TableName}] Razor Component Search Files Created", Color.Success);

                GenerateInertface(setting, columns);

                await console($"Table [{setting.TableName}] Inertface Files Created", Color.Success);

                GenerateService(setting, columns);

                await console($"Table [{setting.TableName}] Service Files Created", Color.Success);

                GenerateRazorPage(setting, columns);

                await console($"Table [{setting.TableName}] Razor Page Files Created", Color.Success);

                await console($"--------->> Table [{setting.TableName}] Generated Success ! <<---------", Color.Warning);

                await Task.Delay(250);

                setting.GenTableCount++;
            }

            return string.Empty;
        }

        /// <summary>
        /// 生成 Entity 数据库实体 
        /// </summary>
        private void GenerateEntity(GenerateSetting setting, List<Column> columns)
        {
            setting.EntityNameSpace = setting.EntityPath.Split("/")[0];
            output = new FileOutput($"{setting.EntityFilePath}\\{setting.TableName}.cs");

            output.WriteLine("using System;");
            output.WriteLine("using System.ComponentModel;");
            output.WriteLine("using System.ComponentModel.DataAnnotations;");
            output.WriteLine("using System.Diagnostics.CodeAnalysis;");
            output.WriteLine("using PetaPoco;\n");

            output.WriteLine($"namespace {setting.EntityNameSpace}");
            output.WriteLine("{");
            output.WriteLine("\t[PrimaryKey(\"id\")]");
            output.WriteLine($"\t[TableName(nameof({setting.TableName}))]");
            output.WriteLine($"\tpublic class {setting.TableName}");
            output.WriteLine("\t{");
            output.WriteLine("        /// <summary>");
            output.WriteLine("        /// 主键");
            output.WriteLine("        /// </summary>");
            output.WriteLine("\t\t[NotNull]");
            output.WriteLine("\t\t[DisplayName(\"主键\")]");
            output.WriteLine("\t\tpublic int id { get; set; }\n");

            for (int i = 0; i < columns.Count; i++)
            {
                output.WriteLine("        /// <summary>");
                output.WriteLine($"        /// 获取或设置 {columns[i].PropertyDesc} {(columns[i].IsNullable ? "Allow Null" : "Not Null")}");
                output.WriteLine("        /// </summary>");

                if (columns[i].IsNullable)
                    output.WriteLine($"\t\t[AllowNull]");
                else
                    output.WriteLine($"\t\t[NotNull]");

                output.WriteLine($"\t\t[DisplayName(\"{columns[i].PropertyDesc}\")]");

                if (!columns[i].IsNullable)
                    output.WriteLine($"\t\t[Required(ErrorMessage = \"" + "{0}" + "不可为空\")]");

                if (columns[i].PropertyType.ToLower() == "string" && columns[i].Length > 0)
                    output.WriteLine($"\t\t[MaxLength({columns[i].Length}, ErrorMessage = \"" + "{0}" + $"不能超过 {columns[i].Length} 个字符\")]");

                if (columns[i].PropertyType == "string")
                    output.WriteLine($"\t\tpublic {columns[i].PropertyType} {columns[i].PropertyCode} " + "{ get; set; }");
                else
                    output.WriteLine($"\t\tpublic {columns[i].PropertyType}{(columns[i].IsNullable ? "?" : "")} {columns[i].PropertyCode} " + "{ get; set; }");

                if (i < columns.Count - 1)
                    output.WriteLine("");
            }
            output.WriteLine("");
            output.WriteLine("        #region 添加的成员\n");
            output.WriteLine("        #endregion");
            output.WriteLine("\t}\n}");
            output.Dispose();
        }

        /// <summary>
        /// 生成 Razor Component 组件 Edit.razor
        /// </summary>
        private void GenerateRazorComponentEdit(GenerateSetting setting, List<Column> columns)
        {
            //生成 Edit.razor
            string InputFields = string.Empty;
            columns.ForEach(t =>
            {
                InputFields += "    <div class=\"col-sm-6 col-md-6\">\n";
                switch (t.InputType)
                {
                    case EnumInputType.Number:
                        InputFields += $"        <BootstrapInputNumber @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" {t.GetNumFormat()} />\n";
                        break;
                    case EnumInputType.DateTime:
                        InputFields += $"        <DateTimePicker @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    case EnumInputType.Boolen:
                        InputFields += $"        <NullSwitch @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    default:
                        InputFields += $"        <BootstrapInput @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                }
                InputFields += "    </div>\n";
            });
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Edit.razor");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Edit.razor.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd().Replace("{GenInputFields}", InputFields.Trim())));
            output.Dispose();

            //生成 Edit.razor.cs
            setting.RazorComponentNameSpace = setting.RazorComponentPath.Replace("/", ".");
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Edit.razor.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Edit.razor.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 生成 Razor Component 组件 Detail.razor
        /// </summary>
        private void GenerateRazorComponentDetail(GenerateSetting setting, List<Column> columns)
        {
            //生成 Detail.razor
            string InputFields = string.Empty;
            columns.ForEach(t =>
            {
                InputFields += "    <div class=\"col-sm-6 col-md-6\">\n";
                switch (t.InputType)
                {
                    case EnumInputType.DateTime:
                        InputFields += $"        <BootstrapInput @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" IsDisabled=\"true\" FormatString=\"yyyy-MM-dd HH:mm:ss\" />\n";
                        break;
                    case EnumInputType.Number:
                        InputFields += $"        <BootstrapInput @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" IsDisabled=\"true\" {t.GetNumFormat()} />\n";
                        break;
                    case EnumInputType.Boolen:
                        InputFields += $"        <NullSwitch @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    default:
                        InputFields += $"        <BootstrapInput @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" IsDisabled=\"true\" />\n";
                        break;
                }
                InputFields += "    </div>\n";
            });
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Detail.razor");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Detail.razor.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd().Replace("{GenInputFields}", InputFields.Trim())));
            output.Dispose();

            //生成 Detail.razor.cs
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Detail.razor.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Detail.razor.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 生成 Razor Model 业务模型类 SearchModel.cs
        /// </summary>
        private void GenerateRazorModel(GenerateSetting setting, List<Column> columns)
        {
            //生成 SearchModel.cs
            string DefineFields = string.Empty, QueryConds = string.Empty, ResetFields = string.Empty;
            columns.Where(t => !t.IsKey).ToList().ForEach(t =>
            {
                DefineFields += $"        [DisplayName(\"{t.PropertyDesc}\")]\n";
                ResetFields += $"            {t.PropertyCode} = null;\n";

                switch (t.InputType)
                {
                    case EnumInputType.DateTime:
                        DefineFields += $"        public DateTimeRangeValue? {t.PropertyCode} " + "{ get; set; }\n\n";

                        QueryConds += $"            if ({t.PropertyCode} != null)\n";
                        QueryConds += "            {\n";
                        QueryConds += $"                ret.Add(new SearchFilterAction(nameof({setting.TableName}.{t.PropertyCode}), {t.PropertyCode}.Start, FilterAction.GreaterThanOrEqual));\n";
                        QueryConds += $"                ret.Add(new SearchFilterAction(nameof({setting.TableName}.{t.PropertyCode}), {t.PropertyCode}.End, FilterAction.LessThanOrEqual));\n";
                        QueryConds += "            }\n\n";
                        break;
                    case EnumInputType.Number:
                        DefineFields += $"        public {t.PropertyType}? {t.PropertyCode} " + "{ get; set; }\n\n";

                        QueryConds += $"            if ({t.PropertyCode}.HasValue)\n";
                        QueryConds += $"                ret.Add(new SearchFilterAction(nameof({setting.TableName}.{t.PropertyCode}), {t.PropertyCode}, FilterAction.Equal));\n\n";
                        break;
                    case EnumInputType.Boolen:
                        DefineFields += $"        public {t.PropertyType}? {t.PropertyCode} " + "{ get; set; }\n\n";

                        QueryConds += $"            if ({t.PropertyCode}.HasValue)\n";
                        QueryConds += $"                ret.Add(new SearchFilterAction(nameof({setting.TableName}.{t.PropertyCode}), {t.PropertyCode}, FilterAction.Equal));\n\n";
                        break;
                    default:
                        DefineFields += $"        public string {t.PropertyCode} " + "{ get; set; }\n\n";

                        QueryConds += $"            if (!string.IsNullOrEmpty({t.PropertyCode}))\n";
                        QueryConds += $"                ret.Add(new SearchFilterAction(nameof({setting.TableName}.{t.PropertyCode}), {t.PropertyCode}));\n\n";
                        break;
                }
            });

            output = new FileOutput($"{setting.RazorModelFilePath}\\{setting.ParscalTableName}SearchModel.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/SearchModel.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()
                    .Replace("{GenDefineFields}", DefineFields.Trim())
                    .Replace("{GenQueryConds}", QueryConds.Trim())
                    .Replace("{GenResetFields}", ResetFields.Trim())));

            output.Dispose();
        }

        /// <summary>
        /// 生成 Razor Component 组件 Search.razor、Search.razor.cs
        /// </summary>
        private void GenerateRazorComponentSearch(GenerateSetting setting, List<Column> columns)
        {
            //生成 Search.razor
            var InputFields = string.Empty;
            columns.Where(t => !t.IsKey).ToList().ForEach(t =>
            {
                InputFields += "    <div class=\"col-sm-6 col-md-6\">\n";
                switch (t.InputType)
                {
                    case EnumInputType.DateTime:
                        InputFields += $"        <DateTimeRange @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    case EnumInputType.Number:
                        InputFields += $"        <BootstrapInputNumber @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    case EnumInputType.Boolen:
                        InputFields += $"        <NullSwitch @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                    default:
                        InputFields += $"        <BootstrapInput @bind-Value=\"Value.{t.PropertyCode}\" ShowLabel=\"true\" />\n";
                        break;
                }
                InputFields += "    </div>\n";
            });
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Search.razor");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Search.razor.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd().Replace("{GenInputFields}", InputFields.Trim())));
            output.Dispose();

            //生成 Search.razor.cs
            output = new FileOutput($"{setting.RazorComponentFilePath}\\{setting.ParscalTableName}Search.razor.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Search.razor.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 生成 接口 Interface.cs
        /// </summary>
        private void GenerateInertface(GenerateSetting setting, List<Column> columns)
        {
            output = new FileOutput($"{setting.InterfaceFilePath}\\I{setting.ParscalTableName}.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Interface.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 生成 服务 Service.cs
        /// </summary>
        private void GenerateService(GenerateSetting setting, List<Column> columns)
        {
            output = new FileOutput($"{setting.ServiceFilePath}\\{setting.ParscalTableName}Service.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Service.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 生成 Razor Page 页面 Page.razor、Page.razor.cs
        /// </summary>
        private void GenerateRazorPage(GenerateSetting setting, List<Column> columns)
        {
            //生成 Razor Page
            var TableColumns = string.Empty;
            columns.Where(t => !t.IsKey).ToList().ForEach(t =>
            {
                TableColumns += $"        <TableColumn @bind-Field=\"context.{t.PropertyCode}\" Filterable=\"true\" Searchable=\"true\" Sortable=\"true\" />\n";
            });
            output = new FileOutput($"{setting.RazorPageFilePath}\\{setting.ParscalTableName}.razor");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Page.razor.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()).Replace("{GenTableColumns}", TableColumns.Trim()));
            output.Dispose();

            //生成 Razor Page Code
            output = new FileOutput($"{setting.RazorPageFilePath}\\{setting.ParscalTableName}.razor.cs");
            using (var reader = new FileInfo(RootPath + "Templates/PetaPocoRazorStyle/Page.razor.cs.txt").OpenText())
                output.WriteLine(ReplaceSysParam(reader.ReadToEnd()));
            output.Dispose();
        }

        /// <summary>
        /// 初始化内置参数
        /// </summary>
        private void InitSysParams(GenerateSetting setting)
        {
            ParamDic = new();
            ParamDic.Add(EnumParam.AppName.ToString(), setting.RootPath);
            ParamDic.Add(EnumParam.EntityNameSpace.ToString(), setting.EntityPath.Split("/")[0]);
            ParamDic.Add(EnumParam.InterfaceNameSpace.ToString(), setting.InterfacePath.Replace("/", "."));
            ParamDic.Add(EnumParam.ServiceNameSpace.ToString(), setting.ServicePath.Replace("/", "."));

            var tmpStr = setting.RazorComponentPath.Replace("/", ".");
            ParamDic.Add(EnumParam.RazorComponentNameSpace.ToString(), tmpStr);
            if (tmpStr.IndexOf("Components.") > 0)
                tmpStr = tmpStr.Substring(0, tmpStr.IndexOf("Components.") + 10);
            ParamDic.Add(EnumParam.SysRazorComponentNameSpace.ToString(), tmpStr);

            ParamDic.Add(EnumParam.RazorModelNameSpace.ToString(), setting.RazorModelPath.Replace("/", "."));
            ParamDic.Add(EnumParam.RazorPageNameSpace.ToString(), setting.RazorPagePath.Replace("/", "."));

            ParamDic.Add(EnumParam.TableName.ToString(), string.Empty);
            ParamDic.Add(EnumParam.ParscalTableName.ToString(), string.Empty);
        }

        /// <summary>
        /// 处理系统参数
        /// </summary>
        private string ReplaceSysParam(string templ)
        {
            var paramList = typeof(EnumParam).ToSelectList();

            paramList.ForEach(t =>
            {
                if (ParamDic.ContainsKey(t.Value))
                    templ = Regex.Replace(templ, "{" + t.Value + "}", ParamDic[t.Value], RegexOptions.Multiline);
            });

            return templ;
        }
    }
}